export interface GameOption {
    width: number;                  // canvas的宽度
    height: number;                 // canvas的高度
    mapColor: string[];             // 地图的俩背景色
    speed?: number;                 // 蛇的速度
    foodColor?: string;             // 食物的颜色
    offset?: number[];              // 偏置量
    cellSize?: number;              // 格子的大小
    cellNumber?: number;            // 格子的数量
}

export enum Direction {
    UP = 1,
    DOWN = 2,
    LEFT = 3,
    RIGHT = 4
}

export enum GameStatus {
    RUN = 1,
    PAUSE = 2,
    STOP = 3,
}

export class Point {
    x: number;
    y: number;

    constructor(x: number, y: number) {
        this.x = x;
        this.y = y;
    }
}

export class Map {
    private readonly option: GameOption;
    private readonly ctx: CanvasRenderingContext2D;

    constructor(option: GameOption, ctx: CanvasRenderingContext2D) {
        this.option = option;
        this.ctx = ctx;
    }

    // 绘制地图
    draw(color?: string[]) {
        if (color) {
            this.option.mapColor = color;
        }
        // 清空区域
        this.ctx.clearRect(0, 0, this.option.width, this.option.height);
        // 绘制网格背景
        this.ctx.beginPath()
        for (let i = 0; i < this.option.cellNumber; i++) {
            for (let j = 0; j < this.option.cellNumber; j++) {
                this.ctx.fillStyle = this.option.mapColor[(i + j) % 2];
                this.ctx.fillRect(this.option.offset[0] + i * this.option.cellSize,
                    this.option.offset[1] + j * this.option.cellSize,
                    this.option.cellSize, this.option.cellSize);
            }
        }
        this.ctx.closePath();
    }
}

export class Food {
    private readonly option: GameOption;
    private readonly ctx: CanvasRenderingContext2D;
    private readonly halfCellSize: number;

    constructor(option: GameOption, ctx: CanvasRenderingContext2D) {
        this.option = option;
        this.ctx = ctx;
        this.halfCellSize = this.option.cellSize / 2;
    }

    // 绘制食物
    draw(point: Point, color?: string) {
        if (color) {
            this.option.foodColor = color;
        }
        // 清空区域
        this.ctx.clearRect(0, 0, this.option.width, this.option.height);
        this.ctx.beginPath();
        this.ctx.fillStyle = this.option.foodColor;
        this.ctx.arc(this.option.offset[0] + point.x * this.option.cellSize + this.halfCellSize,
            this.option.offset[1] + point.y * this.option.cellSize + this.halfCellSize,
            this.halfCellSize, 0, 2 * Math.PI);
        this.ctx.fill();
        this.ctx.closePath();
    }
}

export class Snake {
    public gameStatus: GameStatus = GameStatus.STOP;
    private readonly option: GameOption;
    private readonly snakeCtx: CanvasRenderingContext2D;
    private readonly foodCtx: CanvasRenderingContext2D;
    private readonly halfCellSize: number;
    private food: Food | null = null;
    public foodPoint: Point;
    public score: number
    private snakeBody: Point[] = [new Point(0, 0)]
    private snakeHead: Point = new Point(1, 0)
    private direction: Direction = Direction.RIGHT;
    private timer: number | null | NodeJS.Timeout = null;
    public time: number = 0

    constructor(option: GameOption, snakeCtx: CanvasRenderingContext2D, foodCtx: CanvasRenderingContext2D) {
        this.option = option;
        this.snakeCtx = snakeCtx;
        this.foodCtx = foodCtx;
        this.halfCellSize = this.option.cellSize / 2;
        this.gameStatus = GameStatus.STOP;
        this.drawSnake()
        this.generateFood()
    }

    // 绘制蛇
    drawSnake() {
        // 清空区域
        this.snakeCtx.clearRect(0, 0, this.option.width, this.option.height);
        this.snakeCtx.beginPath();
        this.snakeCtx.fillStyle = '#f07b3f';
        // 绘制蛇头
        this.snakeCtx.arc(
            this.option.offset[0] + this.snakeHead.x * this.option.cellSize + this.halfCellSize,
            this.option.offset[1] + this.snakeHead.y * this.option.cellSize + this.halfCellSize,
            this.halfCellSize, 0, 2 * Math.PI);
        this.snakeCtx.fill();
        this.snakeCtx.fillStyle = '#e84545'
        // 绘制蛇身体
        for (let body of this.snakeBody) {
            this.snakeCtx.fillRect(this.option.offset[0] + body.x * this.option.cellSize,
                this.option.offset[1] + body.y * this.option.cellSize,
                this.option.cellSize, this.option.cellSize)
        }
        this.snakeCtx.closePath();
    }

    // 生成食物
    private generateFood() {
        // 随机生成坐标，不在蛇身上
        while (true) {
            let x = Math.floor(Math.random() * this.option.cellNumber);
            let y = Math.floor(Math.random() * this.option.cellNumber);
            if (!this.snakeBody.some(body => body.x === x && body.y === y)
                && !(x === this.snakeHead.x && y === this.snakeHead.y)) {
                this.foodPoint = new Point(x, y);
                break;
            }
        }
        // 绘制食物
        if (this.food === null) {
            this.food = new Food(this.option, this.foodCtx);
        }
        this.food.draw(this.foodPoint);
    }

    // 碰撞检测
    private detection() {
        // 撞墙
        if (this.snakeHead.x < 0 || this.snakeHead.x > (this.option.cellNumber - 1)
            || this.snakeHead.y < 0 || this.snakeHead.y > (this.option.cellNumber - 1)) {
            this.gameStatus = GameStatus.STOP;
            return;
        }
        // 撞自己
        if (this.snakeBody.some(body => body.x === this.snakeHead.x && body.y === this.snakeHead.y)) {
            this.gameStatus = GameStatus.STOP;
            return;
        }
        // 吃到食物
        if (this.snakeHead.x === this.foodPoint.x && this.snakeHead.y === this.foodPoint.y) {
            this.score++;
            // 蛇身增加
            this.snakeBody.push(new Point(this.snakeBody[this.snakeBody.length - 1].x, this.snakeBody[this.snakeBody.length - 1].y));
            this.generateFood();
        }
    }

    // 移动蛇
    private move() {
        // 复制蛇头
        let newHead = new Point(this.snakeHead.x, this.snakeHead.y);
        // 蛇头移动
        switch (this.direction) {
            case Direction.UP:
                this.snakeHead.y--;
                break;
            case Direction.DOWN:
                this.snakeHead.y++;
                break;
            case Direction.LEFT:
                this.snakeHead.x--;
                break;
            case Direction.RIGHT:
                this.snakeHead.x++;
                break;
        }
        // 删除第一位置
        this.snakeBody.shift();
        // 蛇头添加到蛇身
        this.snakeBody.push(newHead);
    }

    // 开始游戏
    start() {
        if (this.gameStatus === GameStatus.STOP) {
            this.gameStatus = GameStatus.RUN;
            this.score = 0;
            this.snakeBody = [new Point(0, 0)]
            this.snakeHead = new Point(1, 0)
            this.direction = Direction.RIGHT;
            this.drawSnake();
            this.generateFood();
            this.run();
        } else if (this.gameStatus === GameStatus.PAUSE) {
            this.run();
        }
    }

    // 游戏运行
    private run() {
        this.gameStatus = GameStatus.RUN;
        this.timer = setInterval(() => {
            if (this.gameStatus === GameStatus.STOP) {
                clearInterval(this.timer as number);
                return;
            }
            this.time += this.option.speed
            this.detection();
            this.move();
            this.drawSnake();
        }, this.option.speed);
    }

    // 暂停游戏
    pause() {
        this.gameStatus = GameStatus.PAUSE;
        clearInterval(this.timer as number);
    }

    // 设置方向
    setDirection(direction: Direction) {
        // 不能反向移动
        if (this.direction === Direction.UP && direction === Direction.DOWN
            || this.direction === Direction.DOWN && direction === Direction.UP
            || this.direction === Direction.LEFT && direction === Direction.RIGHT
            || this.direction === Direction.RIGHT && direction === Direction.LEFT) {
            return;
        }
        this.direction = direction;
    }

    restart() {
        this.gameStatus = GameStatus.STOP;
        clearInterval(this.timer as number);
        this.start();
    }
}
